#pragma once

#include <iostream>
#include <string>
#include <fstream>
#include <unordered_map>
#include <mutex>
#include "util.hpp"
#include "Log.hpp"
#include <vector>

namespace ns_index
{
  struct DocInfo
  {
    std::string title;
    std::string content;
    std::string url;
    uint64_t doc_id; // 文档的id
  };
  struct InvertedElem
  {
    uint64_t doc_id;
    std::string word;
    int weight;
    InvertedElem()
      :weight(0)
    {}
  };
  //倒排拉链
  typedef std::vector<InvertedElem> InvertedList;

  class Index
  {
    private:
      //正排索引的数据结构直接用数组，数组的下标就是天然的文档id
      std::vector<DocInfo> forward_index;//正排索引
      //倒排索引一定是一个关键字和一组InvertedElem对应【关键词和倒排拉链的映射关系】
      std::unordered_map<std::string,InvertedList> inverted_index;
    private://单例模式
      Index(){}
      Index(const Index&) = delete ;
      Index& operator = (const Index&) = delete ;
    public:
      ~Index(){}
    public:
      static Index* GetInstance()
      {
        if(nullptr == instance)//双重保险
        {
          mtx.lock();
          if(nullptr == instance)
          {
            instance = new Index();
          }
          mtx.unlock();
        }
        return instance;
      }
    public:
      //根据doc_id找到文档内容
      DocInfo* GetForwardIndex(uint64_t doc_id)
      {
        if(doc_id >= forward_index.size())
        {
          LOG(ERROR,"doc_id out range,error!");
          return nullptr;
        }
        return &forward_index[doc_id];
      }

      //根据关键词string，获得倒排拉链
      InvertedList* GetInvertedList(const std::string& word)
      {
        auto iter = inverted_index.find(word);
        if(iter == inverted_index.end())
        {
          LOG(WARNING,"%s have no InvertedList",word.c_str());
          return nullptr;
        }
        return &(iter->second);
      }

      //根据去标签，格式话后的文档，构建正排和倒排索引
      bool BuildIndex(const std::string& input)
      {
        std::ifstream in(input,std::ios::in | std::ios::binary);
        if(!in.is_open())
        {
          LOG(FATAL,"input %s failed",input.c_str());
          return false;
        }

        std::string line;
        int count = 0;
        while(std::getline(in,line))
        {
          DocInfo* doc = BuildForwardIndex(line);
          if(nullptr == doc)
          {
            LOG(WARNING,"bulid %s failed",line.c_str());
            continue;
          }
          BuildInvertedIndex(*doc);
          count++;
          if(count%100 == 0)
          {
            LOG(DEBUG,"create index success num:%d",count);
          }
        }
        return true;
      }
    private:
      //建立正排索引
      DocInfo* BuildForwardIndex(const std::string& line)
      {
        //1. 解析line，字符串切分
        //line 切分为：title content url这3个字符串
        std::vector<std::string> results;
        const std::string sep = "\3";//行内分割符
        ns_util::StringUtil::Split(line,&results,sep);
        if(results.size()!=3)
        {
          return nullptr;
        }
        //2.字符串进行填充到DocInfo
        DocInfo doc;
        doc.title = results[0];
        doc.content = results[1];
        doc.url = results[2];
        doc.doc_id = forward_index.size();//先保存id，再插入，对应的id就是当前doc在vector中的下标
        //3.插入到正排索引的vector
        forward_index.push_back(std::move(doc));
        return &forward_index.back();

      }
      //建立倒排索引
      bool BuildInvertedIndex(const DocInfo& doc)
      {
        struct word_cnt
        {
          int title_cnt;
          int content_cnt;

          word_cnt()
            :title_cnt(0),content_cnt(0)
          {}
        };
        std::unordered_map<std::string,word_cnt> word_map;//临时存储映射表

        //对标题进行分词
        std::vector<std::string> title_words;
        ns_util::JiebaUtil::CutString(doc.title,&title_words);

        //对标题进行词频统计
        for(std::string s:title_words)
        {
          boost::to_lower(s);//转小写
          word_map[s].title_cnt++;
        }

        //对文档内容进行分词
        std::vector<std::string> content_words;
        ns_util::JiebaUtil::CutString(doc.content,&content_words);
        
        //对内容进行词频统计
        for(std::string s : content_words)
        {
          boost::to_lower(s);
          word_map[s].content_cnt++;
        }

#define X 10
#define Y 1

        for(auto&word_pair:word_map)
        {
          InvertedElem item;
          item.doc_id = doc.doc_id;
          item.word = word_pair.first;
          item.weight = X*word_pair.second.title_cnt+Y*word_pair.second.content_cnt;
          InvertedList& inverted_list = inverted_index[word_pair.first];
          inverted_list.push_back(std::move(item));
        }
        return true;

      }
    private:
      static Index* instance;
      static std::mutex mtx;
  };
  Index* Index::instance = nullptr;
  std::mutex Index::mtx;
  

}
